package en.edu.zxj.mq.mqclient;

import en.edu.zxj.mq.common.*;
import en.edu.zxj.mq.common.util.BinaryUtils;
import jdk.internal.util.xml.impl.Input;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created with IntelliJ IDEA.
 * Description：客户端与服务器建立连接类, 用户可以创建多个 Connection 类, 一个 Connection 可以创建多个 Channel
 *
 * @author: zxj
 * @date: 2024-03-06
 * @time: 21:58:16
 */
@Data
@Slf4j
public class Connection {
    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    // 记录当前 Connection 包含的 Channel
    private ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
    // 执行消息回调的线程池
    private ExecutorService callbackPool = null;

    public Connection(String host, Integer port) throws IOException {
        socket = new Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);


        callbackPool = Executors.newFixedThreadPool(4);

        // 创建一个扫描线程, 由这个线程负责不停的从 socket 中读取响应数据, 把这个响应数据再交给对应的 Channel 负责处理
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    Response response = readResponse();
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                // 连接正常断开的. 此时这个异常直接忽略.
                System.out.println("[Connection] 连接正常断开!");
            } catch (IOException | ClassNotFoundException | MqException e) {
                // System.out.println("[Connection] 连接异常断开!");
                log.error("连接异常断开! e: {}", e);
                e.printStackTrace();
            }
        });
        t.start();
    }

    public void close() {
        // 关闭 Connection, 释放相关资源
        try {
            callbackPool.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        } catch (Exception e) {
            log.error("关闭资源出现异常");
            e.printStackTrace();
        }
    }

    /**
     * @description: 读取响应
     **/
    public Response readResponse() throws IOException, MqException {
        log.info("客户端: 开始等待读取消息");

        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());

        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if (n != response.getLength()) {
            throw new MqException("读取的响应数据不完整");
        }
        response.setPayload(payload);

        log.info("收到响应: type: {}, length: {}", response.getType(), response.getLength());

        return response;
    }

    /**
     * @description: 写请求
     **/
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();

        log.info("发送请求:type: {}, length: {}", request.getType(), request.getLength());
    }


    public Channel createChannel() throws IOException {
        // 使用 UUID 生产 channelId, 以 C- 开头
        String channelId = "C-" + UUID.randomUUID().toString();
        Channel channel = new Channel(channelId, this);
        // 这里需要先把 channel 键值放到 Map 中 进行管理
        channelMap.put(channelId, channel);
        // 同时也需要把 "创建 channel" 的这个消息也告诉服务器
        boolean ok = channel.createChannel();
        if (!ok) {
            // 服务器这里创建失败了, 整个这次创建 channel 操作不顺利
            // 把刚才已经加入 hash 表的键值对, 再删了
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }

    /**
     * @description: 使用这个方法来分别处理, 当前的响应是针对控制请求的响应, 还是服务器推送的消息
     **/
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if (response.getType() == 0xc) {
            // 服务器推送来的消息数据
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryUtils.fromBytes(response.getPayload());
            // 根据 ChannelId 找到对应的 Channel 对象
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if (channel == null) {
                throw new MqException("该消息对应的 Channel 在客户端中不存在, channelId: " + subScribeReturns.getChannelId());
            }
            // 执行该 channel 对象内部的回调
            callbackPool.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(), subScribeReturns.getBasicProperties(),
                            subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            // 当前相应是针对刚才的控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryUtils.fromBytes(response.getPayload());

            // 根据 ChannelId 找到对应的 Channel 对象
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if (channel == null) {
                throw new MqException("该消息对应的 Channel 在客户端中不存在, channelId: " + basicReturns.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }

}
